/* eslint-env jquery */
/* eslint-disable new-cap */

'use strict'

var $ = jQuery

$.jqPaginator = function (el, options) {
  if (!(this instanceof $.jqPaginator)) {
    return new $.jqPaginator(el, options)
  }

  var self = this

  self.$container = $(el)

  self.$container.data('jqPaginator', self)

  self.init = function () {
    if (options.first || options.prev || options.next || options.last || options.page) {
      options = $.extend(
        {},
        {
          first: '',
          prev: '',
          next: '',
          last: '',
          page: ''
        },
        options
      )
    }

    self.options = $.extend({}, $.jqPaginator.defaultOptions, options)

    self.verify()

    self.extendJquery()

    self.render()

    self.fireEvent(this.options.currentPage, 'init')
  }

  self.verify = function () {
    var opts = self.options

    if (!self.isNumber(opts.totalPages)) {
      throw new Error('[jqPaginator] type error: totalPages')
    }

    if (!self.isNumber(opts.totalCounts)) {
      throw new Error('[jqPaginator] type error: totalCounts')
    }

    if (!self.isNumber(opts.pageSize)) {
      throw new Error('[jqPaginator] type error: pageSize')
    }

    if (!self.isNumber(opts.currentPage)) {
      throw new Error('[jqPaginator] type error: currentPage')
    }

    if (!self.isNumber(opts.visiblePages)) {
      throw new Error('[jqPaginator] type error: visiblePages')
    }

    if (!opts.totalPages && !opts.totalCounts) {
      throw new Error('[jqPaginator] totalCounts or totalPages is required')
    }

    if (!opts.totalPages && opts.totalCounts && !opts.pageSize) {
      throw new Error('[jqPaginator] pageSize is required')
    }

    if (opts.totalCounts && opts.pageSize) {
      opts.totalPages = Math.ceil(opts.totalCounts / opts.pageSize)
    }

    if (opts.currentPage < 1 || opts.currentPage > opts.totalPages) {
      throw new Error('[jqPaginator] currentPage is incorrect')
    }

    if (opts.totalPages < 1) {
      throw new Error('[jqPaginator] totalPages cannot be less currentPage')
    }
  }

  self.extendJquery = function () {
    $.fn.jqPaginatorHTML = function (s) {
      return s
        ? this.before(s).remove()
        : $('<p>')
          .append(this.eq(0).clone())
          .html()
    }
  }

  self.render = function () {
    self.renderHtml()
    self.setStatus()
    self.bindEvents()
  }

  self.renderHtml = function () {
    var html = []

    var pages = self.getPages()
    for (var i = 0, j = pages.length; i < j; i++) {
      html.push(self.buildItem('page', pages[i]))
    }

    self.isEnable('prev') && html.unshift(self.buildItem('prev', self.options.currentPage - 1))
    self.isEnable('first') && html.unshift(self.buildItem('first', 1))
    self.isEnable('statistics') && html.unshift(self.buildItem('statistics'))
    self.isEnable('next') && html.push(self.buildItem('next', self.options.currentPage + 1))
    self.isEnable('last') && html.push(self.buildItem('last', self.options.totalPages))

    if (self.options.wrapper) {
      self.$container.html(
        $(self.options.wrapper)
          .html(html.join(''))
          .jqPaginatorHTML()
      )
    } else {
      self.$container.html(html.join(''))
    }
  }

  self.buildItem = function (type, pageData) {
    var html = self.options[type]
      .replace(/{{page}}/g, pageData)
      .replace(/{{totalPages}}/g, self.options.totalPages)
      .replace(/{{totalCounts}}/g, self.options.totalCounts)

    return $(html)
      .attr({
        'jp-role': type,
        'jp-data': pageData
      })
      .jqPaginatorHTML()
  }

  self.setStatus = function () {
    var options = self.options

    if (!self.isEnable('first') || options.currentPage === 1) {
      $('[jp-role=first]', self.$container).addClass(options.disableClass)
    }
    if (!self.isEnable('prev') || options.currentPage === 1) {
      $('[jp-role=prev]', self.$container).addClass(options.disableClass)
    }
    if (!self.isEnable('next') || options.currentPage >= options.totalPages) {
      $('[jp-role=next]', self.$container).addClass(options.disableClass)
    }
    if (!self.isEnable('last') || options.currentPage >= options.totalPages) {
      $('[jp-role=last]', self.$container).addClass(options.disableClass)
    }

    $('[jp-role=page]', self.$container).removeClass(options.activeClass)
    $('[jp-role=page][jp-data=' + options.currentPage + ']', self.$container).addClass(options.activeClass)
  }

  self.getPages = function () {
    var pages = []

    var visiblePages = self.options.visiblePages

    var currentPage = self.options.currentPage

    var totalPages = self.options.totalPages

    if (visiblePages > totalPages) {
      visiblePages = totalPages
    }

    var half = Math.floor(visiblePages / 2)
    var start = currentPage - half + 1 - (visiblePages % 2)
    var end = currentPage + half

    if (start < 1) {
      start = 1
      end = visiblePages
    }
    if (end > totalPages) {
      end = totalPages
      start = 1 + totalPages - visiblePages
    }

    var itPage = start
    while (itPage <= end) {
      pages.push(itPage)
      itPage++
    }

    return pages
  }

  self.isNumber = function (value) {
    var type = typeof value
    return type === 'number' || type === 'undefined'
  }

  self.isEnable = function (type) {
    return self.options[type] && typeof self.options[type] === 'string'
  }

  self.switchPage = function (pageIndex) {
    self.options.currentPage = pageIndex
    self.render()
  }

  self.fireEvent = function (pageIndex, type) {
    return typeof self.options.onPageChange !== 'function' || self.options.onPageChange(pageIndex, type) !== false
  }

  self.callMethod = function (method, options) {
    switch (method) {
      case 'option':
        self.options = $.extend({}, self.options, options)
        self.verify()
        self.render()
        break
      case 'destroy':
        self.$container.empty()
        self.$container.removeData('jqPaginator')
        break
      default:
        throw new Error('[jqPaginator] method "' + method + '" does not exist')
    }

    return self.$container
  }

  self.bindEvents = function () {
    var opts = self.options

    self.$container.off()
    self.$container.on('click', '[jp-role]', function () {
      var $el = $(this)
      if ($el.hasClass(opts.disableClass) || $el.hasClass(opts.activeClass)) {
        return
      }

      var pageIndex = +$el.attr('jp-data')
      if (self.fireEvent(pageIndex, 'change')) {
        self.switchPage(pageIndex)
      }
    })
  }

  self.init()

  return self.$container
}

$.jqPaginator.defaultOptions = {
  wrapper: '',
  first: '<li class="first"><a href="javascript:;">First</a></li>',
  prev: '<li class="prev"><a href="javascript:;">Previous</a></li>',
  next: '<li class="next"><a href="javascript:;">Next</a></li>',
  last: '<li class="last"><a href="javascript:;">Last</a></li>',
  page: '<li class="page"><a href="javascript:;">{{page}}</a></li>',
  totalPages: 0,
  totalCounts: 0,
  pageSize: 0,
  currentPage: 1,
  visiblePages: 7,
  disableClass: 'disabled',
  activeClass: 'active',
  onPageChange: null
}

$.fn.jqPaginator = function () {
  var self = this

  var args = Array.prototype.slice.call(arguments)

  if (typeof args[0] === 'string') {
    var $instance = $(self).data('jqPaginator')
    if (!$instance) {
      throw new Error('[jqPaginator] the element is not instantiated')
    } else {
      return $instance.callMethod(args[0], args[1])
    }
  } else {
    return new $.jqPaginator(this, args[0])
  }
}
